/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "Precompiled.h"
#include "globaldata.h"



#include <stdlib.h>
#include <math.h>


#include "doomdef.h"
#include "d_net.h"

#include "m_bbox.h"

#include "r_local.h"
#include "r_sky.h"
#include "i_system.h"




// Fineangles in the SCREENWIDTH wide window.




// increment every time a check is made





// just for profiling purposes






// 0 = high, 1 = low

//
// precalculated math tables
//

// The ::g->viewangletox[::g->viewangle + FINEANGLES/4] lookup
// maps the visible view angles to screen X coordinates,
// flattening the arc to a flat ::g->projection plane.
// There will be many angles mapped to the same X.

// The xtoviewangleangle[] table maps a screen pixel
// to the lowest ::g->viewangle that maps back to x ranges
// from ::g->clipangle to -::g->clipangle.


// UNUSED.
// The finetangentgent[angle+FINEANGLES/4] table
// holds the fixed_t tangent values for view angles,
// ranging from MININT to 0 to MAXINT.
// fixed_t		finetangent[FINEANGLES/2];

// fixed_t		finesine[5*FINEANGLES/4];
const fixed_t*		finecosine = &finesine[FINEANGLES / 4];



// bumped light from gun blasts



void ( *colfunc )( lighttable_t* dc_colormap,
				   byte* dc_source );
void ( *basecolfunc )( lighttable_t* dc_colormap,
					   byte* dc_source );
void ( *fuzzcolfunc )( lighttable_t* dc_colormap,
					   byte* dc_source );
void ( *transcolfunc )( lighttable_t* dc_colormap,
						byte* dc_source );
void ( *spanfunc )( fixed_t xfrac,
					fixed_t yfrac,
					fixed_t ds_y,
					int ds_x1,
					int ds_x2,
					fixed_t ds_xstep,
					fixed_t ds_ystep,
					lighttable_t* ds_colormap,
					byte* ds_source );



//
// R_AddPointToBox
// Expand a given bbox
// so that it encloses a given point.
//
void
R_AddPointToBox
( int		x,
  int		y,
  fixed_t*	box )
{
	if( x < box[BOXLEFT] )
	{
		box[BOXLEFT] = x;
	}
	if( x > box[BOXRIGHT] )
	{
		box[BOXRIGHT] = x;
	}
	if( y < box[BOXBOTTOM] )
	{
		box[BOXBOTTOM] = y;
	}
	if( y > box[BOXTOP] )
	{
		box[BOXTOP] = y;
	}
}


//
// R_PointOnSide
// Traverse BSP (sub) tree,
//  check point against partition plane.
// Returns side 0 (front) or 1 (back).
//
int
R_PointOnSide
( fixed_t	x,
  fixed_t	y,
  node_t*	node )
{
	fixed_t	dx;
	fixed_t	dy;
	fixed_t	left;
	fixed_t	right;

	if( !node->dx )
	{
		if( x <= node->x )
		{
			return node->dy > 0;
		}

		return node->dy < 0;
	}
	if( !node->dy )
	{
		if( y <= node->y )
		{
			return node->dx < 0;
		}

		return node->dx > 0;
	}

	dx = ( x - node->x );
	dy = ( y - node->y );

	// Try to quickly decide by looking at sign bits.
	if( ( node->dy ^ node->dx ^ dx ^ dy ) & 0x80000000 )
	{
		if( ( node->dy ^ dx ) & 0x80000000 )
		{
			// (left is negative)
			return 1;
		}
		return 0;
	}

	left = FixedMul( node->dy >> FRACBITS , dx );
	right = FixedMul( dy , node->dx >> FRACBITS );

	if( right < left )
	{
		// front side
		return 0;
	}
	// back side
	return 1;
}


int
R_PointOnSegSide
( fixed_t	x,
  fixed_t	y,
  seg_t*	line )
{
	fixed_t	lx;
	fixed_t	ly;
	fixed_t	ldx;
	fixed_t	ldy;
	fixed_t	dx;
	fixed_t	dy;
	fixed_t	left;
	fixed_t	right;

	lx = line->v1->x;
	ly = line->v1->y;

	ldx = line->v2->x - lx;
	ldy = line->v2->y - ly;

	if( !ldx )
	{
		if( x <= lx )
		{
			return ldy > 0;
		}

		return ldy < 0;
	}
	if( !ldy )
	{
		if( y <= ly )
		{
			return ldx < 0;
		}

		return ldx > 0;
	}

	dx = ( x - lx );
	dy = ( y - ly );

	// Try to quickly decide by looking at sign bits.
	if( ( ldy ^ ldx ^ dx ^ dy ) & 0x80000000 )
	{
		if( ( ldy ^ dx ) & 0x80000000 )
		{
			// (left is negative)
			return 1;
		}
		return 0;
	}

	left = FixedMul( ldy >> FRACBITS , dx );
	right = FixedMul( dy , ldx >> FRACBITS );

	if( right < left )
	{
		// front side
		return 0;
	}
	// back side
	return 1;
}


//
// R_PointToAngle
// To get a global angle from cartesian coordinates,
//  the coordinates are flipped until they are in
//  the first octant of the coordinate system, then
//  the y (<=x) is scaled and divided by x to get a
//  tangent (slope) value which is looked up in the
//  tantoangle[] table.

//




angle_t
R_PointToAngle
( fixed_t	x,
  fixed_t	y )
{
	extern fixed_t GetViewX();
	extern fixed_t GetViewY();
	x -= GetViewX();
	y -= GetViewY();

	if( ( !x ) && ( !y ) )
	{
		return 0;
	}

	if( x >= 0 )
	{
		// x >=0
		if( y >= 0 )
		{
			// y>= 0

			if( x > y )
			{
				// octant 0
				return tantoangle[ SlopeDiv( y, x )];
			}
			else
			{
				// octant 1
				return ANG90 - 1 - tantoangle[ SlopeDiv( x, y )];
			}
		}
		else
		{
			// y<0
			y = -y;

			if( x > y )
			{
				// octant 8
				return UINT_MAX - tantoangle[SlopeDiv( y, x )] + 1; // ALANHACK UNSIGNED, SRS - make uint math explicit
			}
			else
			{
				// octant 7
				return ANG270 + tantoangle[ SlopeDiv( x, y )];
			}
		}
	}
	else
	{
		// x<0
		x = -x;

		if( y >= 0 )
		{
			// y>= 0
			if( x > y )
			{
				// octant 3
				return ANG180 - 1 - tantoangle[ SlopeDiv( y, x )];
			}
			else
			{
				// octant 2
				return ANG90 + tantoangle[ SlopeDiv( x, y )];
			}
		}
		else
		{
			// y<0
			y = -y;

			if( x > y )
			{
				// octant 4
				return ANG180 + tantoangle[ SlopeDiv( y, x )];
			}
			else
			{
				// octant 5
				return ANG270 - 1 - tantoangle[ SlopeDiv( x, y )];
			}
		}
	}
	return 0;
}


angle_t
R_PointToAngle2
( fixed_t	x1,
  fixed_t	y1,
  fixed_t	x2,
  fixed_t	y2 )
{
	extern void SetViewX( fixed_t );
	extern void SetViewY( fixed_t );
	SetViewX( x1 );
	SetViewY( y1 );

	return R_PointToAngle( x2, y2 );
}


fixed_t
R_PointToDist
( fixed_t	x,
  fixed_t	y )
{
	int		angle;
	fixed_t	dx;
	fixed_t	dy;
	fixed_t	temp;
	fixed_t	dist;

	extern fixed_t GetViewX();
	extern fixed_t GetViewY();
	dx = abs( x - GetViewX() );
	dy = abs( y - GetViewY() );

	if( dy > dx )
	{
		temp = dx;
		dx = dy;
		dy = temp;
	}

	angle = ( tantoangle[ FixedDiv( dy, dx ) >> DBITS ] + ANG90 ) >> ANGLETOFINESHIFT;

	// use as cosine
	dist = FixedDiv( dx, finesine[angle] );

	return dist;
}




//
// R_InitPointToAngle
//
void R_InitPointToAngle( void )
{
	// UNUSED - now getting from tables.c
#if 0
	int	i;
	long	t;
	float	f;
	//
	// slope (tangent) to angle lookup
	//
	for( i = 0 ; i <= SLOPERANGE ; i++ )
	{
		f = atan( ( float )i / SLOPERANGE ) / ( 3.141592657 * 2 );
		t = 0xffffffff * f;
		tantoangle[i] = t;
	}
#endif
}


//
// R_ScaleFromGlobalAngle
// Returns the texture mapping scale
//  for the current line (horizontal span)
//  at the given angle.
// ::g->rw_distance must be calculated first.
//
fixed_t R_ScaleFromGlobalAngle( angle_t visangle )
{
	fixed_t		scale;
	//int			anglea;
	//int			angleb;
	angle_t		anglea;
	angle_t		angleb;
	int			sinea;
	int			sineb;
	fixed_t		num;
	int			den;

	// UNUSED
#if 0
	{
		fixed_t		dist;
		fixed_t		z;
		fixed_t		sinv;
		fixed_t		cosv;

		sinv = finesine[( visangle -::g->rw_normalangle ) >> ANGLETOFINESHIFT];
		dist = FixedDiv( ::g->rw_distance, sinv );
		cosv = finecosine[( ::g->viewangle - visangle ) >> ANGLETOFINESHIFT];
		z = abs( FixedMul( dist, cosv ) );
		scale = FixedDiv( ::g->projection, z );
		return scale;
	}
#endif

	extern angle_t GetViewAngle();
	anglea = ANG90 + ( visangle - GetViewAngle() );
	angleb = ANG90 + ( visangle -::g->rw_normalangle );

	// both sines are allways positive
	sinea = finesine[anglea >> ANGLETOFINESHIFT];
	sineb = finesine[angleb >> ANGLETOFINESHIFT];
	num = FixedMul( ::g->projection, sineb ) << ::g->detailshift;
	den = FixedMul( ::g->rw_distance, sinea );

	// DHM - Nerve :: If the den is pretty much 0, don't try the divide
	if( den >> 8 > 0 && den > num >> 16 )
	{
		scale = FixedDiv( num, den );

		if( scale > 64 * FRACUNIT )
		{
			scale = 64 * FRACUNIT;
		}
		else if( scale < 256 )
		{
			scale = 256;
		}
	}
	else
	{
		scale = 64 * FRACUNIT;
	}

	return scale;
}



//
// R_InitTables
//
void R_InitTables( void )
{
	// UNUSED: now getting from tables.c
#if 0
	int		i;
	float	a;
	float	fv;
	int		t;

	// ::g->viewangle tangent table
	for( i = 0 ; i < FINEANGLES / 2 ; i++ )
	{
		a = ( i - FINEANGLES / 4 + 0.5 ) * PI * 2 / FINEANGLES;
		fv = FRACUNIT * tan( a );
		t = fv;
		finetangent[i] = t;
	}

	// finesine table
	for( i = 0 ; i < 5 * FINEANGLES / 4 ; i++ )
	{
		// OPTIMIZE: mirror...
		a = ( i + 0.5 ) * PI * 2 / FINEANGLES;
		t = FRACUNIT * sin( a );
		finesine[i] = t;
	}
#endif

}



//
// R_InitTextureMapping
//
void R_InitTextureMapping( void )
{
	int			i;
	int			x;
	int			t;
	fixed_t		focallength;

	// Use tangent table to generate viewangletox:
	//  ::g->viewangletox will give the next greatest x
	//  after the view angle.
	//
	// Calc focallength
	//  so FIELDOFVIEW angles covers SCREENWIDTH.
	focallength = FixedDiv( ::g->centerxfrac,
							finetangent[FINEANGLES / 4 + FIELDOFVIEW / 2] );

	for( i = 0 ; i < FINEANGLES / 2 ; i++ )
	{
		if( finetangent[i] > FRACUNIT * 2 )
		{
			t = -1;
		}
		else if( finetangent[i] < -FRACUNIT * 2 )
		{
			t = ::g->viewwidth + 1;
		}
		else
		{
			t = FixedMul( finetangent[i], focallength );
			t = ( ::g->centerxfrac - t + FRACUNIT - 1 ) >> FRACBITS;

			if( t < -1 )
			{
				t = -1;
			}
			else if( t >::g->viewwidth + 1 )
			{
				t = ::g->viewwidth + 1;
			}
		}
		::g->viewangletox[i] = t;
	}

	// Scan ::g->viewangletox[] to generate ::g->xtoviewangle[]:
	//  ::g->xtoviewangle will give the smallest view angle
	//  that maps to x.
	for( x = 0; x <=::g->viewwidth; x++ )
	{
		i = 0;
		while( ::g->viewangletox[i] > x )
		{
			i++;
		}
		::g->xtoviewangle[x] = ( i << ANGLETOFINESHIFT ) - ANG90;
	}

	// Take out the fencepost cases from ::g->viewangletox.
	for( i = 0 ; i < FINEANGLES / 2 ; i++ )
	{
		t = FixedMul( finetangent[i], focallength );
		t = ::g->centerx - t;

		if( ::g->viewangletox[i] == -1 )
		{
			::g->viewangletox[i] = 0;
		}
		else if( ::g->viewangletox[i] == ::g->viewwidth + 1 )
		{
			::g->viewangletox[i]  = ::g->viewwidth;
		}
	}

	::g->clipangle = ::g->xtoviewangle[0];
}



//
// R_InitLightTables
// Only inits the ::g->zlight table,
//  because the ::g->scalelight table changes with view size.
//

void R_InitLightTables( void )
{
	int		i;
	int		j;
	int		level;
	int		nocollide_startmap;
	int		scale;

	// Calculate the light levels to use
	//  for each level / distance combination.
	for( i = 0 ; i < LIGHTLEVELS ; i++ )
	{
		nocollide_startmap = ( ( LIGHTLEVELS - 1 - i ) * 2 ) * NUMCOLORMAPS / LIGHTLEVELS;
		for( j = 0 ; j < MAXLIGHTZ ; j++ )
		{
			scale = FixedDiv( ( SCREENWIDTH / 2 * FRACUNIT ), ( j + 1 ) << LIGHTZSHIFT );
			scale >>= LIGHTSCALESHIFT;
			level = nocollide_startmap - scale / DISTMAP;

			if( level < 0 )
			{
				level = 0;
			}

			if( level >= NUMCOLORMAPS )
			{
				level = NUMCOLORMAPS - 1;
			}

			::g->zlight[i][j] = ::g->colormaps + level * 256;
		}
	}
}



//
// R_SetViewSize
// Do not really change anything here,
//  because it might be in the middle of a refresh.
// The change will take effect next refresh.
//


void
R_SetViewSize
( int		blocks,
  int		detail )
{
	::g->setsizeneeded = true;
	::g->setblocks = blocks;
	::g->setdetail = detail;
}


//
// R_ExecuteSetViewSize
//
void R_ExecuteSetViewSize( void )
{
	fixed_t	cosadj;
	fixed_t	dy;
	int		i;
	int		j;
	int		level;
	int		nocollide_startmap;

	::g->setsizeneeded = false;

	if( ::g->setblocks == 11 )
	{
		::g->scaledviewwidth = ORIGINAL_WIDTH;
		::g->viewheight = ORIGINAL_HEIGHT;
	}
	else
	{
		::g->scaledviewwidth = ::g->setblocks * 32;
		::g->viewheight = ( ::g->setblocks * 168 / 10 ) & ~7;
	}

	// SMF - temp
	::g->scaledviewwidth *= GLOBAL_IMAGE_SCALER;
	::g->viewheight *= GLOBAL_IMAGE_SCALER;

	::g->detailshift = ::g->setdetail;
	::g->viewwidth = ::g->scaledviewwidth >>::g->detailshift;

	::g->centery = ::g->viewheight / 2;
	::g->centerx = ::g->viewwidth / 2;
	::g->centerxfrac = ::g->centerx << FRACBITS;
	::g->centeryfrac = ::g->centery << FRACBITS;
	::g->projection = ::g->centerxfrac;

	if( !::g->detailshift )
	{
		colfunc = basecolfunc = R_DrawColumn;
		fuzzcolfunc = R_DrawFuzzColumn;
		transcolfunc = R_DrawTranslatedColumn;
		spanfunc = R_DrawSpan;
	}
	else
	{
		colfunc = basecolfunc = R_DrawColumnLow;
		fuzzcolfunc = R_DrawFuzzColumn;
		transcolfunc = R_DrawTranslatedColumn;
		spanfunc = R_DrawSpanLow;
	}

	R_InitBuffer( ::g->scaledviewwidth, ::g->viewheight );

	R_InitTextureMapping();

	// psprite scales
	::g->pspritescale = FRACUNIT*::g->viewwidth / ORIGINAL_WIDTH;
	::g->pspriteiscale = FRACUNIT * ORIGINAL_WIDTH /::g->viewwidth;

	// thing clipping
	for( i = 0 ; i < ::g->viewwidth ; i++ )
	{
		::g->screenheightarray[i] = ::g->viewheight;
	}

	// planes
	for( i = 0 ; i < ::g->viewheight ; i++ )
	{
		dy = ( ( i -::g->viewheight / 2 ) << FRACBITS ) + FRACUNIT / 2;
		dy = abs( dy );
		::g->yslope[i] = FixedDiv( ( ::g->viewwidth << ::g->detailshift ) / 2 * FRACUNIT, dy );
	}

	for( i = 0 ; i < ::g->viewwidth ; i++ )
	{
		cosadj = abs( finecosine[::g->xtoviewangle[i] >> ANGLETOFINESHIFT] );
		::g->distscale[i] = FixedDiv( FRACUNIT, cosadj );
	}

	// Calculate the light levels to use
	//  for each level / scale combination.
	for( i = 0 ; i < LIGHTLEVELS ; i++ )
	{
		nocollide_startmap = ( ( LIGHTLEVELS - 1 - i ) * 2 ) * NUMCOLORMAPS / LIGHTLEVELS;
		for( j = 0 ; j < MAXLIGHTSCALE ; j++ )
		{
			level = nocollide_startmap - j * SCREENWIDTH / ( ::g->viewwidth << ::g->detailshift ) / DISTMAP;

			if( level < 0 )
			{
				level = 0;
			}

			if( level >= NUMCOLORMAPS )
			{
				level = NUMCOLORMAPS - 1;
			}

			::g->scalelight[i][j] = ::g->colormaps + level * 256;
		}
	}
}



//
// R_Init
//



void R_Init( void )
{
	R_InitData();
	I_Printf( "\nR_InitData" );
	R_InitPointToAngle();
	I_Printf( "\nR_InitPointToAngle" );
	R_InitTables();
	// ::g->viewwidth / ::g->viewheight / ::g->detailLevel are set by the defaults
	I_Printf( "\nR_InitTables" );

	R_SetViewSize( ::g->screenblocks, ::g->detailLevel );
	R_InitPlanes();
	I_Printf( "\nR_InitPlanes" );
	R_InitLightTables();
	I_Printf( "\nR_InitLightTables" );
	R_InitSkyMap();
	I_Printf( "\nR_InitSkyMap" );
	R_InitTranslationTables();
	I_Printf( "\nR_InitTranslationsTables" );

	::g->framecount = 0;
}


//
// R_PointInSubsector
//
subsector_t*
R_PointInSubsector
( fixed_t	x,
  fixed_t	y )
{
	node_t*	node;
	int		side;
	int		nodenum;

	// single subsector is a special case
	if( !::g->numnodes )
	{
		return ::g->subsectors;
	}

	nodenum = ::g->numnodes - 1;

	while( !( nodenum & NF_SUBSECTOR ) )
	{
		node = &::g->nodes[nodenum];
		side = R_PointOnSide( x, y, node );
		nodenum = node->children[side];
	}

	return &::g->subsectors[nodenum & ~NF_SUBSECTOR];
}



//
// R_SetupFrame
//
void R_SetupFrame( player_t* player )
{
	int		i;

	::g->viewplayer = player;
	extern void SetViewX( fixed_t );
	extern void SetViewY( fixed_t );
	extern void SetViewAngle( angle_t );
	SetViewX( player->mo->x );
	SetViewY( player->mo->y );
	SetViewAngle( player->mo->angle + ::g->viewangleoffset );
	::g->extralight = player->extralight;

	::g->viewz = player->viewz;

	extern angle_t GetViewAngle();

	::g->viewsin = finesine[GetViewAngle() >> ANGLETOFINESHIFT];
	::g->viewcos = finecosine[GetViewAngle() >> ANGLETOFINESHIFT];

	::g->sscount = 0;

	if( player->fixedcolormap )
	{
		::g->fixedcolormap =
			::g->colormaps
			+ player->fixedcolormap * 256 * sizeof( lighttable_t );

		::g->walllights = ::g->scalelightfixed;

		for( i = 0 ; i < MAXLIGHTSCALE ; i++ )
		{
			::g->scalelightfixed[i] = ::g->fixedcolormap;
		}
	}
	else
	{
		::g->fixedcolormap = 0;
	}

	::g->framecount++;
	::g->validcount++;
}



//
// R_RenderView
//
void R_RenderPlayerView( player_t* player )
{
	if( player->mo == NULL )
	{
		return;
	}

	R_SetupFrame( player );

	// Clear buffers.
	R_ClearClipSegs();
	R_ClearDrawSegs();
	R_ClearPlanes();
	R_ClearSprites();

	// check for new console commands.
	NetUpdate( NULL );

	// The head node is the last node output.
	R_RenderBSPNode( ::g->numnodes - 1 );

	// Check for new console commands.
	NetUpdate( NULL );

	R_DrawPlanes();

	// Check for new console commands.
	NetUpdate( NULL );

	R_DrawMasked();

	// Check for new console commands.
	NetUpdate( NULL );
}

